home *** CD-ROM | disk | FTP | other *** search
/ CD ROM Paradise Collection 4 / CD ROM Paradise Collection 4 1995 Nov.iso / program / swaga_c.zip / COMM.SWG / 0054_Sending Chars to the COM.pas < prev    next >
Pascal/Delphi Source File  |  1994-08-25  |  10KB  |  374 lines

  1. {
  2. > I have a Turbo Pascal "interrupt" routine which "catches" incoming
  3. > characters from a COM port and stashes them in a circular buffer.
  4. > While it seems to work OK most of the time, occasionally it misses
  5. > a character (it can NOT keep up with 600 baud, but Kermit does quite
  6. > well at 9600 baud, so I know it can be "fixed").  Here is the code:
  7. > (Please ignore the BEGINPROCEDURE, ENDIF, etc.; I use a pre-processor
  8. > to translate Pascal-as-it-ought-to-be-IMHO into Turbo Pascal.)
  9. >
  10. I don't know what the trouble is with your interrupt routine, but I wrote one
  11. about 6 months ago for a friend to use and it works fine on my machine (386
  12. 33) at 2400 and on my friends machine (486 66) at 9600.  Here it is, hope it
  13. helps.
  14.  
  15. This unit is an array implementation of a queue, used to store incoming
  16. characters.  An array is used instead of a linked list because I believed it
  17. would be faster, and less overhead.
  18. }
  19. UNIT QPak;
  20. {$R-}
  21. {Range checking must be turned off, so as to permit the little trick with
  22. the array}
  23.  
  24. INTERFACE
  25.  
  26. TYPE
  27.   ElementType = Char;
  28.  
  29.   ElementArray = ARRAY[0..0] OF Char;
  30.  
  31.   QUEUE   = RECORD
  32.     Front,
  33.     Rear  : Word;
  34.     EL    : ^ElementArray;
  35.     Size  : Word;
  36.     Count : Word;
  37.   END;
  38.  
  39. PROCEDURE MakeQueueEmpty(VAR Q : Queue;
  40.                          QSize : Word);
  41.  
  42. FUNCTION  QueueIsEmpty(Q : Queue) : Boolean;
  43.  
  44. FUNCTION  QueueIsFull(Q : Queue) : Boolean;
  45.  
  46. PROCEDURE Enqueue(VAR Q   : Queue;
  47.                   Element : ElementType);
  48.  
  49. PROCEDURE Dequeue(VAR Q       : Queue;
  50.                   VAR Element : ElementType);
  51.  
  52. IMPLEMENTATION
  53.  
  54.  
  55. PROCEDURE MakeQueueEmpty(VAR Q : Queue; QSize : Word);
  56.  
  57. BEGIN
  58.   GetMem(Q.EL,QSize);
  59.   Q.Front := 1;
  60.   Q.Rear  := 0;
  61.   Q.Size  := QSize;
  62.   Q.Count := 0;
  63. END;
  64.  
  65. FUNCTION QueueIsEmpty(Q : Queue) : Boolean;
  66.  
  67. BEGIN
  68.   QueueIsEmpty := (Q.Count = 0);
  69. END;
  70.  
  71. FUNCTION QueueIsFull(Q : Queue) : Boolean;
  72.  
  73. BEGIN
  74.   QueueIsFull := (Q.Count = Q.Size);
  75. END;
  76.  
  77.  
  78. PROCEDURE Enqueue(VAR Q : Queue; Element : ElementType);
  79.  
  80. BEGIN
  81.   WITH Q Do BEGIN
  82.     Rear := (Rear + 1) MOD Size;
  83.     EL^[Rear] := Element;
  84.     Inc(Count);
  85.   END;
  86. END;
  87.  
  88. PROCEDURE Dequeue(VAR Q : Queue; VAR Element : ElementType);
  89.  
  90. BEGIN
  91.   WITH Q DO BEGIN
  92.     Element := EL^[Front];
  93.     Front := (Front + 1) MOD Size;
  94.     Dec(Count);
  95.   END;
  96. END;
  97.  
  98. END.
  99. {
  100. -----------------------CUT HERE--------------------
  101.  
  102. Here is the com unit.  I've commented about everyline (since it was for a
  103. friend) so hopefilly my comments are understandable.
  104.  
  105. -----------------------CUT HERE---------------------
  106. }
  107. UNIT ComUnit;
  108.  
  109. INTERFACE
  110.  
  111. USES DOS, CRT, QPak;
  112.  
  113. PROCEDURE InitPort(ComPort,
  114.                    Parity,
  115.                    Stop,
  116.                    WLength : Byte;
  117.                    Speed   : Word);
  118.  
  119. FUNCTION CharReady(ComPort : Byte) : Boolean;
  120.  
  121. {This procedure  writes a char to desired port}
  122. PROCEDURE SendChar(Ch : Char; ComPort : Byte);
  123.  
  124. {This function reads a char from the serial port by dequeueing and element}
  125. FUNCTION GetChar(ComPort : Byte) : Char;
  126.  
  127. PROCEDURE ShutDown(ComPort : Byte);
  128.  
  129. TYPE
  130.   UART = RECORD
  131.      THR : Integer; {Transmit Holding Register}
  132.      RBR : Integer; {Receive Holding Register}
  133.      IER : Integer; {Interrupt enable Regeister}
  134.      LCR : Word;    {Line Control Register}
  135.      MCR : Integer; {Modem Control Register}
  136.      LSR : Integer; {Line Status Register}
  137.      MSR : Integer; {Modem Status Register}
  138.      IRQ : Integer;
  139.      DLL : Word;
  140.      DLM : WOrd;
  141.   END;
  142.  
  143.   {This array holds the buffers for each port}
  144.   BufferArray  = ARRAY[1..4] OF Queue;
  145.   {Here is where we save the old interrupt vectors}
  146.   PointerArray = ARRAY[1..4] OF Pointer;
  147.  
  148.  
  149. CONST
  150. {The following are constants used in initialization, and for port adressing}
  151.   COM1 = 1;
  152.   COM2 = 2;
  153.   COM3 = 3;
  154.   COM4 = 4;
  155.  
  156. {Baud rate divisors}
  157.   B600  = 192;
  158.   B1200 = 96;
  159.   B2400 = 48;
  160.   B4800 = 24;
  161.   B9600 = 12;
  162.   B19200 = 6;
  163.   B38400 = 3;  {If your really feeling frisky}
  164.  
  165. {Parity masks}
  166.   NoParity   = 0;
  167.   OddParity  = $8;
  168.   EvenParity = $18;
  169.  
  170. {Stop bit masks}
  171.   OneStopBit = 0;
  172.   TwoStopBit = 2;
  173.  
  174. {OR-Mask to set divisor latch in line control register}
  175.   DLatch      = $80;
  176.  
  177. {Port address for interrupt mask port of 8259A}
  178.   IntMaskPort = $21;
  179.  
  180. {Port address for 8259 interrupt control, used to send EOI}
  181.   IntCtlPort  = $20;
  182.  
  183. {Masks for different word lengths}
  184.   Word5 = 0;
  185.   Word6 = 1;
  186.   Word7 = 2;
  187.   Word8 = 3;
  188.  
  189. IMPLEMENTATION
  190.  
  191. CONST
  192. {Typed constant that contains all registers addresses for Com1..Com4}
  193.   RS232 : ARRAY[1..4] OF UART =
  194.  
  195. ((THR:$3F8;RBR:$3F8;IER:$3F9;LCR:$3FB;MCR:$3FC;LSR:$3FD;MSR:$3FE;IRQ:4;DLL:$3F8
  196. ;
  197. LM:$3F9),
  198.  
  199. (THR:$2F8;RBR:$2F8;IER:$2F9;LCR:$2FB;MCR:$2FC;LSR:$2FD;MSR:$2FE;IRQ:3;DLL:$2F8;
  200. LM:$2F9),
  201.  
  202. (THR:$3E8;RBR:$3E8;IER:$3E9;LCR:$3EB;MCR:$3EC;LSR:$3ED;MSR:$3EE;IRQ:4;DLL:$3E8;
  203. LM:$3E9),
  204.  
  205. (THR:$2E8;RBR:$2E8;IER:$2E9;LCR:$2EB;MCR:$2EC;LSR:$2ED;MSR:$2EE;IRQ:3;DLL:$2E8;
  206. LM:$2E9));
  207.  
  208.  
  209. VAR
  210.   Buffers     : BufferArray;
  211.   IntVecsSave : PointerArray;
  212.  
  213. {Inline Macros}
  214. PROCEDURE DisableInterrupts ;   inline( $FA {cli} ) ;
  215. PROCEDURE EnableInterrupts ;    inline( $FB {sti} ) ;
  216.  
  217. {Here is the interrupt procedure for com3, its address is put int the int
  218.  Vec table by InitPort}
  219. PROCEDURE Com13ISR; INTERRUPT;
  220.  
  221. BEGIN
  222. {Read the character from the port and put it in the queue}
  223.   Enqueue(Buffers[Com3],Char(Port[RS232[Com3].RBR]));
  224.   Port[IntCtlPort] := $20;  {Non-specific EOI}
  225. END;
  226.  
  227. PROCEDURE Com24ISR; INTERRUPT;
  228.  
  229. BEGIN
  230. {Read the character from the port and put it in the queue}
  231.   Enqueue(Buffers[Com3],Char(Port[RS232[Com3].RBR]));
  232.   Port[IntCtlPort] := $20;  {Non-specific EOI}
  233. END;
  234.  
  235. {---------------------------------------------------------------}
  236. {                        +++InitPort+++                         }
  237. {                                                               }
  238. {  ComPort: A byte specifying the comport to use Range 1..4     }
  239. {  Speed  : This is really the baud rate divisor The predefined }
  240. {           constants are the correct divisors for those speeds }
  241. {  Parity,                                                      }
  242. {  Stop,                                                        }
  243. {  WLength: These are all bit-masks used to build               }
  244. {           the line format byte                                }
  245. {---------------------------------------------------------------}
  246. PROCEDURE InitPort(ComPort,
  247.                    Parity,
  248.                    Stop,
  249.                    WLength : Byte;
  250.                    Speed   : Word);
  251.  
  252. VAR
  253.   LineFormat : Byte;
  254.  
  255.  
  256. BEGIN
  257.   MakeQueueEmpty(Buffers[ComPort],2048);
  258.   LineFormat := 0;
  259. {Build the line format byte}
  260.   LineFormat := LineFormat OR WLength OR Stop OR Parity;
  261. {Set divisor latch so we can set baud rate}
  262.   Port[RS232[ComPort].LCR] := LineFormat AND DLatch;
  263. {Now we set baud rate, least sig part of divisor sent first then most sig}
  264.   Port[RS232[ComPort].DLL] := Low(Speed);
  265.   Port[RS232[ComPort].DLM] := Hi(Speed);
  266. {Now set line format}
  267.   Port[RS232[ComPort].LCR] := LineFormat;
  268. {Must set out2 of modem control reg for interrupts, so we do it here}
  269.   Port[RS232[ComPort].MCR] := $0B;
  270. {Save interrupt vector so we can restore it later, then set vector to
  271.  point at our ISR}
  272.  
  273. {Now we must unmask appropriate int line in 8259A interrupt controller
  274.  We are using IRQ4 for com1 and 3, and IRQ3 for com2 and 4, use of any
  275.  other IRQ line will require changes to the code}
  276.   IF ODD(ComPort) THEN BEGIN
  277.     GetIntVec(RS232[ComPort].IRQ+8,IntVecsSave[ComPort]);
  278.     SetIntVec(RS232[ComPort].IRQ+8,@Com13ISR);
  279.     Port[IntMaskPort] := Port[IntMaskPort] AND $EF
  280.   END ELSE BEGIN
  281.     GetIntVec(RS232[ComPort].IRQ+8,IntVecsSave[ComPort]);
  282.     SetIntVec(RS232[ComPort].IRQ+8,@Com24ISR);
  283.     Port[IntMaskPort] := Port[IntMaskPort] AND $F7;
  284.   END;
  285. {Here we tell 8250 UART to interrupt on received chars}
  286.   DisableInterrupts;
  287.     Port[Rs232[ComPort].IER] := 1;
  288.   EnableInterrupts;
  289.  
  290. END;
  291.  
  292. {This function returns true if there are any chars in the buffer}
  293. FUNCTION CharReady(ComPort : Byte) : Boolean;
  294.  
  295. BEGIN
  296.   CharReady := NOT QueueIsEmpty(Buffers[ComPort]);
  297. END;
  298.  
  299. {This procedure  writes a char to desired port}
  300. PROCEDURE SendChar(Ch : Char; ComPort : Byte);
  301.  
  302. BEGIN
  303.   {Loop until transmit holding register empty}
  304.   WHILE (Port[RS232[ComPort].LSR] AND $20) <> $20 DO
  305.     Delay(1);
  306.   Port[RS232[ComPort].THR] := Byte(Ch);
  307. END;
  308.  
  309. {This function reads a char from the serial port by dequeueing and element}
  310. FUNCTION GetChar(ComPort : Byte) : Char;
  311.  
  312. VAR
  313.   Ch : Char;
  314.  
  315. BEGIN
  316.   Dequeue(Buffers[ComPort],Ch);
  317.   GetChar := Ch;
  318. END;
  319.  
  320.  
  321. PROCEDURE ShutDown(ComPort : Byte);
  322.  
  323. BEGIN
  324.   SetIntVec(RS232[ComPort].IRQ+8,IntVecsSave[ComPort]);
  325. END;
  326.  
  327. END.
  328.  
  329. {
  330. ------------------CUT HERE---------------------
  331.  
  332. One remark is probably appropriate here.  My friend had the need to read two
  333. ports simultaneously so that is why there are two interrupt rountine, one com
  334. 1 and 3 and one for com 2 and 4, since they use the same IRQ lines.
  335.  
  336. Here is a little test program I used.
  337.  
  338. -----------------CUT HERE----------------------
  339. }
  340. USES CRT, ComUnit;
  341. VAR
  342.  Ch   : Char;
  343.  Done : Boolean;
  344.  
  345. BEGIN
  346.   Done := FALSE;
  347.   InitPort(Com3,NoParity,OneStopBit,Word8,B2400);
  348.   ClrScr;
  349.   Writeln('Com test in progress.  F1 to exit');
  350.   REPEAT
  351.     IF CharReady(Com3) THEN BEGIN
  352.       Ch := GetChar(Com3);
  353.       Write(Ch);
  354.     END
  355.     ELSE IF Keypressed THEN BEGIN
  356.       Ch := ReadKey;
  357.       IF CH = #0 THEN BEGIN {Extended key scan code}
  358.         Ch := Readkey;
  359.         IF Ch = #59 THEN  {F1}
  360.           Done := True;
  361.       END ELSE
  362.         SendChar(Ch,Com3);
  363.     END
  364.  UNTIL Done;
  365.  ShutDown(Com3);
  366. END.
  367. {
  368.  
  369. I hope this helps.  It does work, although there could some thing wrong given
  370. I'm no expert.  I also wrote some routines in assember about a year and a
  371. half ago, so if you really want assembly code I'd be happy to did them out.
  372. }
  373.  
  374.